/*
 * @(#)PNGImageDecoder.java	1.17 06/10/10
 *
 * Copyright  1990-2008 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 *
 */

package sun.awt.image;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.awt.image.*;
import java.awt.Color;

/** PNG - Portable Network Graphics - image file reader.
    See <a href=ftp://ds.internic.net/rfc/rfc2083.txt>RFC2083</a> for details. */

/* this is changed
public class PNGImageDecoder extends FilterInputStream implements Runnable
{ */

public class PNGImageDecoder extends ImageDecoder
{
    private static final int GRAY=0;
    private static final int PALETTE=1;
    private static final int COLOR=2;
    private static final int ALPHA=4;

    private static final int bKGDChunk = 0x624B4744;
    private static final int cHRMChunk = 0x6348524D;
    private static final int gAMAChunk = 0x67414D41;
    private static final int hISTChunk = 0x68495354;
    private static final int IDATChunk = 0x49444154;
    private static final int IENDChunk = 0x49454E44;
    private static final int IHDRChunk = 0x49484452;
    private static final int PLTEChunk = 0x504C5445;
    private static final int pHYsChunk = 0x70485973;
    private static final int sBITChunk = 0x73424954;
    private static final int tEXtChunk = 0x74455874;
    private static final int tIMEChunk = 0x74494D45;
    private static final int tRNSChunk = 0x74524E53;
    private static final int zTXtChunk = 0x7A545874;

    private int width;
    private int height;
    private int bitDepth;
    private int colorType;
    private int compressionMethod;
    private int filterMethod;
    private int interlaceMethod;
    private int gamma = 100000;
    // 4679080 - create instance of properties as in JPEG and GIF image architecture.
    private Hashtable properties = new Hashtable();
    /* this is not needed
     ImageConsumer target;
     */
    private ColorModel cm;
    private byte[] red_map, green_map, blue_map, alpha_map;
    private int transparentPixel = -1;
    private static ColorModel greyModels[] = new ColorModel[4];
    /* this is not needed
     PNGImageDecoder next;
     */

    private void property(String key, Object value) {
        if (value == null) return;
        properties.put(key, value);
    }

    private void property(String key, float value) {
        property(key, new Float(value));
    }

    private final void verify(boolean b) throws IOException {
        if (!b) {
            PNGException e = new PNGException("Broken file");
            e.printStackTrace();
            throw e;
        }
    }

    protected boolean handleChunk(int key, byte[] buf, int st, int len)
        throws IOException {
        switch (key) {
        case bKGDChunk:
            Color c = null;
            switch (colorType) {
            case COLOR:
            case COLOR | ALPHA:
                verify(len == 6);
                c = new Color(buf[st] & 0xff, buf[st + 2] & 0xff, buf[st + 4] & 0xff);
                break;

            case COLOR | PALETTE:
            case COLOR | PALETTE | ALPHA:
                verify(len == 1);
                int ix = buf[st] & 0xFF;
                verify(red_map != null && ix < red_map.length);
                c = new Color(red_map[ix] & 0xff, green_map[ix] & 0xff, blue_map[ix] & 0xff);
                break;

            case GRAY:
            case GRAY | ALPHA:
                verify(len == 2);
                int t = buf[st] & 0xFF;
                c = new Color(t, t, t);
                break;
            }
            if (c != null) property("background", c);
            break;

        case cHRMChunk:
            property("chromaticities",
                new Chromaticities(
                    getInt(st),
                    getInt(st + 4),
                    getInt(st + 8),
                    getInt(st + 12),
                    getInt(st + 16),
                    getInt(st + 20),
                    getInt(st + 24),
                    getInt(st + 28)));
            break;

        case gAMAChunk:
            if (len != 4) throw new PNGException("bogus gAMA");
            gamma = getInt(st);
            if (gamma != 100000) property("gamma", gamma / 100000.0f);
            break;

        case hISTChunk:
            break;

        case IDATChunk:
            return false;

        case IENDChunk:
            break;

        case IHDRChunk:
            if (len != 13
                || (width = getInt(st)) == 0
                || (height = getInt(st + 4)) == 0
            ) throw new PNGException("bogus IHDR");
            bitDepth = getByte(st + 8);
            colorType = getByte(st + 9);
            compressionMethod = getByte(st + 10);
            filterMethod = getByte(st + 11);
            interlaceMethod = getByte(st + 12);
            /* this is not needed
             if(target!=null) target.setDimensions(width,height);
             */
            break;

        case PLTEChunk: {
                int tsize = len / 3;
                red_map = new byte[tsize];
                green_map = new byte[tsize];
                blue_map = new byte[tsize];
                for (int i = 0, j = st; i < tsize; i++, j += 3) {
                    red_map[i] = buf[j];
                    green_map[i] = buf[j + 1];
                    blue_map[i] = buf[j + 2];
                }
            }
            break;

        case pHYsChunk:
            break;

        case sBITChunk:
            break;

        case tEXtChunk:
            int klen = 0;
            while (klen < len && buf[st + klen] != 0) klen++;
            if (klen < len) {
                String tkey = new String(buf, st, klen);
                String tvalue = new String(buf, st + klen + 1, len - klen - 1);
                property(tkey, tvalue);
            }
            break;

        case tIMEChunk:
            property("modtime", new GregorianCalendar(
                    getShort(st + 0),
                    getByte(st + 2) - 1,
                    getByte(st + 3),
                    getByte(st + 4),
                    getByte(st + 5),
                    getByte(st + 6)).getTime());
            break;

        case tRNSChunk:
            switch (colorType) {
            case PALETTE | COLOR:
            case PALETTE | COLOR | ALPHA:
                int alen = len;
                if (red_map != null) alen = red_map.length;
                alpha_map = new byte[alen];
                System.arraycopy(buf, st, alpha_map, 0, len < alen ? len : alen);
                while (--alen >= len) alpha_map[alen] = (byte) 0xFF;
                break;

            case COLOR: // doesn't deal with 16 bit colors properly
            case COLOR | ALPHA: // doesn't deal with 16 bit colors properly
                verify(len == 6);
                transparentPixel =
                        ((buf[st + 0] & 0xFF) << 16)
                        | ((buf[st + 2] & 0xFF) << 8)
                        | ((buf[st + 4] & 0xFF));
                break;

            case GRAY:  // doesn't deal with 16 bit colors properly
            case GRAY | ALPHA:  // doesn't deal with 16 bit colors properly
                verify(len == 2);
                int t = buf[st] & 0xFF;
                transparentPixel = (t << 16) | (t << 8) | t;
                break;
            }
            break;

        case zTXtChunk:
            break;
        }
        return true;
    }
    public class PNGException extends IOException {
        PNGException(String s) {
            super(s);
        }
    }
    /* this is changed
     public void run() {
     */
    public void produceImage() throws IOException, ImageFormatException {
        /* this is not needed
         ImageConsumer t = target;
         if(t!=null) try {
         */
        try {
            for (int i = 0; i < signature.length; i++)
                if ((signature[i] & 0xFF) != underlyingInputStream.read())
                    throw new PNGException("Chunk signature mismatch");
            InputStream is = new BufferedInputStream(new InflaterInputStream(inputStream, new Inflater()));
            getData();
            byte[] bPixels = null;
            int[] wPixels = null;
            int pixSize = width;
            int rowStride;
            int logDepth = 0;
            switch (bitDepth) {
            case  1:
                logDepth = 0;
                break;

            case  2:
                logDepth = 1;
                break;

            case  4:
                logDepth = 2;
                break;

            case  8:
                logDepth = 3;
                break;

            case 16:
                logDepth = 4;
                break;

            default:
                throw new PNGException("invalid depth");
            }
            if (interlaceMethod != 0) {
                pixSize *= height;
                rowStride = width;
            } else rowStride = 0;
            int combinedType = colorType | (bitDepth << 3);
            int bitMask = (1 << (bitDepth >= 8 ? 8 : bitDepth)) - 1;
            //Figure out the color model
            switch (colorType) {
            case COLOR | PALETTE:
            case COLOR | PALETTE | ALPHA:
                if (red_map == null) throw new PNGException("palette expected");
                if (alpha_map == null)
                    cm = new IndexColorModel(bitDepth, red_map.length,
                                red_map, green_map, blue_map);
                else
                    cm = new IndexColorModel(bitDepth, red_map.length,
                                red_map, green_map, blue_map, alpha_map);
                bPixels = new byte[pixSize];
                break;

            case GRAY: {
                    int llog = logDepth >= 4 ? 3 : logDepth;
                    if ((cm = greyModels[llog]) == null) {
                        int size = 1 << (1 << llog);
                        byte ramp[] = new byte[size];
                        for (int i = 0; i < size; i++) ramp[i] = (byte) (255 * i / (size - 1));
                        cm = new IndexColorModel(bitDepth, ramp.length, ramp, ramp, ramp);
                        greyModels[llog] = cm;
                    }
                }
                bPixels = new byte[pixSize];
                break;

            case COLOR:
            case COLOR | ALPHA:
            case GRAY | ALPHA:
                cm = ColorModel.getRGBdefault();
                wPixels = new int[pixSize];
                break;

            default:
                throw new PNGException("invalid color type");
            }
            /* this is going to be set in the pixel store
             t.setColorModel(cm);
             t.setHints(interlaceMethod !=0
             ? ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES
             : ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
             ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
             */
            // code added to make it work with ImageDecoder architecture
            setDimensions(width, height);
            setColorModel(cm);
            // 4679080 - set properties as in JPEG and GIF image architecture.
            setProperties(properties);
            // end fix - 4679080
            
            int flags = (interlaceMethod != 0
                    ? ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES
                    : ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
                    ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
            setHints(flags);
            headerComplete();
            // end of adding

            int samplesPerPixel = ((colorType & PALETTE) != 0 ? 1
                    : ((colorType & COLOR) != 0 ? 3 : 1) + ((colorType & ALPHA) != 0 ? 1 : 0));
            int bitsPerPixel = samplesPerPixel * bitDepth;
            int bytesPerPixel = (bitsPerPixel + 7) >> 3;
            int pass, passLimit;
            if (interlaceMethod == 0) {
                pass = -1;
                passLimit = 0;
            } else {
                pass = 0;
                passLimit = 7;
            }
            // These loops are far from being tuned.  They're this way to make them easy to
            // debug.  Tuning comes later.
            /* code changed. target not needed here
             while(++pass<=passLimit && (t=target)!=null) {
             */
            while (++pass <= passLimit) {
                int row = startingRow[pass];
                int rowInc = rowIncrement[pass];
                int colInc = colIncrement[pass];
                //int bWidth = blockWidth[pass];
                //int bHeight = blockHeight[pass];
                int sCol = startingCol[pass];
                int rowPixelWidth = (width - sCol + (colInc - 1)) / colInc;
                int rowByteWidth = ((rowPixelWidth * bitsPerPixel) + 7) >> 3;
                if (rowByteWidth == 0) continue;
                int rowOffset = rowStride * row;
                boolean firstRow = true;
                
                byte[] rowByteBuffer = new byte[rowByteWidth];
                byte[] prevRowByteBuffer = new byte[rowByteWidth];
                /* code changed. target not needed here 
                 while (row < height && (t=target)!=null) {
                 */
                while (row < height) {
                    int rowFilter = is.read();
                    for (int rowFillPos = 0; rowFillPos < rowByteWidth;) {
                        int n = is.read(rowByteBuffer, rowFillPos, rowByteWidth - rowFillPos);
                        if (n <= 0) throw new PNGException("missing data");
                        rowFillPos += n;
                    }
                    filterRow(rowByteBuffer,
                              firstRow ? null : prevRowByteBuffer,
                              rowFilter, rowByteWidth, bytesPerPixel);
                    int col = sCol;
                    int spos = 0;
                    while (col < width) {
                        if (wPixels != null) {
                            switch (combinedType) {
                            case COLOR | ALPHA | (8 << 3):
                                wPixels[col + rowOffset] =
                                        ((rowByteBuffer[spos  ] & 0xFF) << 16)
                                        | ((rowByteBuffer[spos + 1] & 0xFF) << 8)
                                        | ((rowByteBuffer[spos + 2] & 0xFF))
                                        | ((rowByteBuffer[spos + 3] & 0xFF) << 24);
                                spos += 4;
                                break;

                            case COLOR | ALPHA | (16 << 3):
                                wPixels[col + rowOffset] =
                                        ((rowByteBuffer[spos  ] & 0xFF) << 16)
                                        | ((rowByteBuffer[spos + 2] & 0xFF) << 8)
                                        | ((rowByteBuffer[spos + 4] & 0xFF))
                                        | ((rowByteBuffer[spos + 6] & 0xFF) << 24);
                                spos += 8;
                                break;

                            case COLOR | (8 << 3):
                                wPixels[col + rowOffset] =
                                        ((rowByteBuffer[spos  ] & 0xFF) << 16)
                                        | ((rowByteBuffer[spos + 1] & 0xFF) << 8)
                                        | ((rowByteBuffer[spos + 2] & 0xFF))
                                        | ((0xFF) << 24);
                                spos += 3;
                                break;

                            case COLOR | (16 << 3):
                                wPixels[col + rowOffset] =
                                        ((rowByteBuffer[spos  ] & 0xFF) << 16)
                                        | ((rowByteBuffer[spos + 2] & 0xFF) << 8)
                                        | ((rowByteBuffer[spos + 4] & 0xFF))
                                        | ((0xFF) << 24);
                                spos += 6;
                                break;

                            case GRAY | ALPHA | (8 << 3): {
                                    int tx = rowByteBuffer[spos] & 0xFF;
                                    wPixels[col + rowOffset] =
                                            (tx << 16) | (tx << 8) | tx
                                            | ((rowByteBuffer[spos + 1] & 0xFF) << 24);
                                }
                                spos += 2;
                                break;

                            case GRAY | ALPHA | (16 << 3): {
                                    int tx = rowByteBuffer[spos] & 0xFF;
                                    wPixels[col + rowOffset] =
                                            (tx << 16) | (tx << 8) | tx
                                            | ((rowByteBuffer[spos + 2] & 0xFF) << 24);
                                }
                                spos += 4;
                                break;

                            default:
                                throw new PNGException("illegal type/depth");
                            }
                        } else switch (bitDepth) {
                            case 1:
                                bPixels[col + rowOffset] =
                                        (byte) ((rowByteBuffer[spos >> 3] >> (7 - (spos & 7))) & 1);
                                spos++;
                                break;

                            case 2:
                                bPixels[col + rowOffset] =
                                        (byte) ((rowByteBuffer[spos >> 2] >> ((3 - (spos & 3)) * 2)) & 3);
                                spos++;
                                break;

                            case 4:
                                bPixels[col + rowOffset] =
                                        (byte) ((rowByteBuffer[spos >> 1] >> ((1 - (spos & 1)) * 4)) & 7);
                                spos++;
                                break;

                            case 8:
                                bPixels[col + rowOffset] = rowByteBuffer[spos++];
                                break;

                            case 16:
                                bPixels[col + rowOffset] = rowByteBuffer[spos];
                                spos += 2;
                                break;

                            default:
                                throw new PNGException("illegal type/depth");
                            }
                            /*visit (row, col,
                             min (bHeight, height - row),
                             min (bWidth, width - col)); */
                        col += colInc;
                    }
                    if (interlaceMethod == 0) 
                        if (wPixels != null) {
                            /* code changed. target not needed here
                             t.setPixels(0,row,width,1,cm,wPixels,0,width); 
                             */
                            // code added to make it work with ImageDecoder arch
                            sendPixels(0, row, width, 1, wPixels, 0, width); 
                            // end of adding
                        } else {
                            /* code changed. target not needed here
                             t.setPixels(0,row,width,1,cm,bPixels,0,width);
                             */
                            // code added to make it work with ImageDecoder arch
                            sendPixels(0, row, width, 1, bPixels, 0, width);
                            //end of adding
                        }
                    row += rowInc;
                    rowOffset += rowInc * rowStride;
                    byte[] T = rowByteBuffer;
                    rowByteBuffer = prevRowByteBuffer;
                    prevRowByteBuffer = T;
                    firstRow = false;
                }
                if (interlaceMethod != 0)
                    if (wPixels != null) {
                        /* code changed. target not needed here
                         t.setPixels(0,0,width,height,cm,wPixels,0,width); 
                         */
                        // code added to make it work with ImageDecoder arch
                        sendPixels(0, 0, width, height, wPixels, 0, width); 
                        //end of adding
                    } else {
                        /* code changed. target not needed here
                         t.setPixels(0,0,width,height,cm,bPixels,0,width);
                         */
                        // code added to make it work with ImageDecoder arch
                        sendPixels(0, 0, width, height, bPixels, 0, width);
                        //end of adding
                    }
            }
            /* Here, the function "visit(row,column,height,width)" obtains the
             next transmitted pixel and paints a rectangle of the specified
             height and width, whose upper-left corner is at the specified row
             and column, using the color indicated by the pixel.  Note that row
             and column are measured from 0,0 at the upper left corner. */
  
	    /* code not needed, don't deal with target
             if((t=target)!=null) {
	       if(properties!=null) t.setProperties(properties);
                 t.imageComplete(ImageConsumer.STATICIMAGEDONE);
		 */

            imageComplete(ImageConsumer.STATICIMAGEDONE, true);
            /* code not needed }
             is.close();
             */
        } catch (Throwable e) {
            /* code not needed
             if((t=target)!=null) {
             PNGEncoder.prChunk(e.toString(),inbuf,pos,limit-pos,true);
             */
            property("error", e);
            /* code not needed
             t.setProperties(properties);
             t.imageComplete(ImageConsumer.IMAGEERROR|ImageConsumer.STATICIMAGEDONE);
             */
            imageComplete(ImageConsumer.IMAGEERROR | ImageConsumer.STATICIMAGEDONE, true);
            //   }
            e.printStackTrace();
        } finally { 
            try {
                close();
            } catch (Throwable e) {}
            /* code not needed
             target = null;
             endTurn();
             */
        }
    }

    private boolean sendPixels(int x, int y, int w, int h, int[] pixels,
                               int offset, int pixlength) {
	int count = setPixels(x, y, w, h, cm,
			      pixels, offset, pixlength);
	if (count <= 0) {
	    aborted = true;
	}
	return !aborted;
    }

    private boolean sendPixels(int x, int y, int w, int h, byte[] pixels,
                               int offset, int pixlength) {
	int count = setPixels(x, y, w, h, cm,
			      pixels, offset, pixlength);
	if (count <= 0) {
	    aborted = true;
	}
	return !aborted;
    }

    private void filterRow(byte rowByteBuffer[], byte[] prevRow,
                           int rowFilter, int rowByteWidth, int bytesPerSample)
        throws IOException {
        int x = 0;
        switch (rowFilter) {
        case 0:
            break;

        case 1:
            for (x = bytesPerSample; x < rowByteWidth; x++)
                rowByteBuffer[x] += rowByteBuffer[x - bytesPerSample];
            break;

        case 2:
            if (prevRow != null)
                for (; x < rowByteWidth; x++) 
                    rowByteBuffer[x] += prevRow[x];
            break;

        case 3:
            if (prevRow != null) {
                for (; x < bytesPerSample; x++)
                    rowByteBuffer[x] += (0xff & prevRow[x]) >> 1;
                for (; x < rowByteWidth; x++)
                    rowByteBuffer[x] += ((prevRow[x] & 0xFF) + (rowByteBuffer[x - bytesPerSample] & 0xFF)) >> 1;
            } else
                for (x = bytesPerSample; x < rowByteWidth; x++)
                    rowByteBuffer[x] += (rowByteBuffer[x - bytesPerSample] & 0xFF) >> 1;
            break;

        case 4:
            if (prevRow != null) {
                for (; x < bytesPerSample; x++)
                    rowByteBuffer[x] += prevRow[x];
                for (; x < rowByteWidth; x++) {
                    int a, b, c, p, pa, pb, pc;
                    a = rowByteBuffer[x - bytesPerSample] & 0xFF;
                    b = prevRow[x] & 0xFF;
                    c = prevRow[x - bytesPerSample] & 0xFF;
                    p = a + b - c;
                    pa = p > a ? p - a : a - p;
                    pb = p > b ? p - b : b - p;
                    pc = p > c ? p - c : c - p;
                    rowByteBuffer[x] += (pa <= pb) && (pa <= pc) ? a : pb <= pc ? b : c;
                }
            } else
                for (x = bytesPerSample; x < rowByteWidth; x++)
                    rowByteBuffer[x] += rowByteBuffer[x - bytesPerSample];
            break;

        default:
            throw new PNGException("Illegal filter");
        }
    }
    private static final byte[] startingRow =  { 0, 0, 0, 4, 0, 2, 0, 1 };
    private static final byte[] startingCol =  { 0, 0, 4, 0, 2, 0, 1, 0 };
    private static final byte[] rowIncrement = { 1, 8, 8, 8, 4, 4, 2, 2 };
    private static final byte[] colIncrement = { 1, 8, 8, 4, 4, 2, 2, 1 };
    private static final byte[] blockHeight =  { 1, 8, 8, 4, 4, 2, 2, 1 };
    private static final byte[] blockWidth =   { 1, 8, 4, 4, 2, 2, 1, 1 };
    
    //abstract public class ChunkReader extends FilterInputStream {
    int pos, limit;
    int chunkStart;
    int chunkKey, chunkLength, chunkCRC;
    boolean seenEOF;
    
    private static final byte[] signature = { (byte) 137, (byte) 80, (byte) 78,
        (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10 };

    PNGFilterInputStream inputStream;
    InputStream underlyingInputStream;

    /* code changed
     public PNGImageDecoder(InputStream in, ImageConsumer t) throws IOException {
     */
    public PNGImageDecoder(InputStreamImageSource src, InputStream input) throws IOException {
        // code added
        super(src, input);
        inputStream = new PNGFilterInputStream(this, input);
        underlyingInputStream = inputStream.underlyingInputStream;
        // end of adding
        /* code changed
         super(in);
         target = t;
         waitTurn();
         new Thread(this).start();
         */
    }
    /* code changed to make it work with ImageDecoder architecture
     static int ThreadLimit = 10;
     private synchronized static void waitTurn() {
     try {
     while(ThreadLimit<=0) PNGImageDecoder.class.wait(1000);
     } catch(InterruptedException e){}
     ThreadLimit--;
     }
     private synchronized static void endTurn() {
     if(ThreadLimit<=0) PNGImageDecoder.class.notify();
     ThreadLimit++;
     }
     */
    byte[] inbuf = new byte[4096];
    private void fill() throws IOException {
        if (!seenEOF) {
            if (pos > 0 && pos < limit) {
                System.arraycopy(inbuf, pos, inbuf, 0, limit - pos);
                limit = limit - pos;
                pos = 0;
            } else if (pos >= limit) {
                pos = 0;
                limit = 0;
            }
            int bsize = inbuf.length;
            while (limit < bsize) {
                int n = underlyingInputStream.read(inbuf, limit, bsize - limit);
                if (n <= 0) {
                    seenEOF = true;
                    break;
                }
                limit += n;
            }
        }
    }

    private boolean need(int n) throws IOException {
        if (limit - pos >= n) return true;
        fill();
        if (limit - pos >= n) return true;
        if (seenEOF) return false;
        byte nin[] = new byte[n + 100];
        System.arraycopy(inbuf, pos, nin, 0, limit - pos);
        limit = limit - pos;
        pos = 0;
        inbuf = nin;
        fill();
        return limit - pos >= n;
    }

    private final int getInt(int pos) {
        return ((inbuf[pos  ] & 0xFF) << 24)
            | ((inbuf[pos + 1] & 0xFF) << 16)
            | ((inbuf[pos + 2] & 0xFF) << 8)
            | ((inbuf[pos + 3] & 0xFF));
    }

    private final int getShort(int pos) {
        return (short) (((inbuf[pos  ] & 0xFF) << 8)
                | ((inbuf[pos + 1] & 0xFF)));
    }

    private final int getByte(int pos) {
        return inbuf[pos] & 0xFF;
    }

    private final boolean getChunk() throws IOException {
        chunkLength = 0;
        if (!need(8)) return false;
        chunkLength = getInt(pos);
        chunkKey = getInt(pos+4);
        if(chunkLength<0) throw new PNGException("bogus length: "+chunkLength);
        if (!need(chunkLength+12)) return false;
        chunkCRC = getInt(pos+8+chunkLength);
        chunkStart = pos+8;
        int calcCRC = crc(inbuf,pos+4,chunkLength+4);
        if(chunkCRC!=calcCRC && checkCRC) throw new PNGException("crc corruption");
        pos+=chunkLength+12;
        return true;
    }

    private void readAll() throws IOException {
        while (getChunk()) handleChunk(chunkKey, inbuf, chunkStart, chunkLength);
    }

    boolean getData() throws IOException {
        while (chunkLength == 0 && getChunk())
            if (handleChunk(chunkKey, inbuf, chunkStart, chunkLength))
                chunkLength = 0;
        return chunkLength > 0;
    }
    //abstract protected boolean handleChunk(int key, byte[] buf, int st, int len)
    //    throws IOException;
    private static boolean checkCRC = true;
    public static boolean getCheckCRC() {
        return checkCRC;
    }

    public static void setCheckCRC(boolean c) {
        checkCRC = c;
    }

    protected void wrc(int c) {
        c = c & 0xFF;
        if (c <= ' ' || c > 'z') c = '?';
        System.out.write(c);
    }

    protected void wrk(int n) {
        wrc(n >> 24);
        wrc(n >> 16);
        wrc(n >> 8);
        wrc(n);
    }

    public void print() {
        wrk(chunkKey);
        System.out.print(" " + chunkLength + "\n");
    }
    /* Table of CRCs of all 8-bit messages. */
    private static final int[] crc_table = new int[256];

    /* Make the table for a fast CRC. */
    static {
        for (int n = 0; n < 256; n++) {
            int c = n;
            for (int k = 0; k < 8; k++)
                if ((c & 1) != 0)
                    c = 0xedb88320 ^ (c >>> 1);
                else
                    c = c >>> 1;
            crc_table[n] = c;
        }
    }

    /* Update a running CRC with the bytes buf[0..len-1]--the CRC
     should be initialized to all 1's, and the transmitted value
     is the 1's complement of the final running CRC (see the
     crc() routine below)). */

    static private int update_crc(int crc, byte[] buf, int offset, int len) {
        int c = crc;
        while (--len >= 0)
            c = crc_table[(c ^ buf[offset++]) & 0xff] ^ (c >>> 8);
        return c;
    }

    /* Return the CRC of the bytes buf[0..len-1]. */
    static private int crc(byte[] buf, int offset, int len) {
        return update_crc(0xffffffff, buf, offset, len) ^ 0xffffffff;
    }
    public static class Chromaticities {
        public float whiteX, whiteY, redX, redY, greenX, greenY, blueX, blueY;
        Chromaticities(int wx, int wy, int rx, int ry, int gx, int gy, int bx, int by) {
            whiteX = wx / 100000.0f;
            whiteY = wy / 100000.0f;
            redX = rx / 100000.0f;
            redY = ry / 100000.0f;
            greenX = gx / 100000.0f;
            greenY = gy / 100000.0f;
            blueX = bx / 100000.0f;
            blueY = by / 100000.0f;
        }

        public String toString() {
            return "Chromaticities(white=" + whiteX + "," + whiteY + ";red=" +
                redX + "," + redY + ";green=" +
                greenX + "," + greenY + ";blue=" +
                blueX + "," + blueY + ")";
        }
    }
}

// the following class are added to make it work with ImageDecoder architecture

class PNGFilterInputStream extends FilterInputStream {
    PNGImageDecoder owner;
    public InputStream underlyingInputStream;
    public PNGFilterInputStream(PNGImageDecoder owner, InputStream is) {
        super(is);
        underlyingInputStream = in;
        this.owner = owner;
    }
  
    public int available() throws IOException {
        return owner.limit - owner.pos + in.available();
    }

    public boolean markSupported() {
        return false;
    }

    public int read() throws IOException {
        if (owner.chunkLength <= 0) if (!owner.getData()) return -1;
        owner.chunkLength--;
        return owner.inbuf[owner.chunkStart++] & 0xFF;
    }

    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte[] b, int st, int len) throws IOException {
        if (owner.chunkLength <= 0) if (!owner.getData()) return -1;
        if (owner.chunkLength < len) len = owner.chunkLength;
        System.arraycopy(owner.inbuf, owner.chunkStart, b, st, len);
        owner.chunkLength -= len;
        owner.chunkStart += len;
        return len;
    }

    public long skip(long n) throws IOException {
        int i;
        for (i = 0; i < n && read() >= 0; i++);
        return i;
    } 
}
